package com.area42games.grammar;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonErrorNode;

import com.area42games.grammar.trees.DiceTree;
import com.area42games.grammar.trees.EvalContext;
import com.area42games.grammar.trees.InvalidReferenceException;

public class DiceParserDriver 
{
	public static DiceTree getDiceTree(EvalContext context, String function) throws RecognitionException
	{
		DiceTree tree = null;

		CharStream input = new ANTLRStringStream(function);
		DiceLexer lexer = new DiceLexer(input);
		CommonTokenStream tokens = new CommonTokenStream(lexer);
		DiceTreeAdaptor adaptor = new DiceTreeAdaptor(context);
		DiceParser parser = new DiceParser(tokens);
		parser.setTreeAdaptor(adaptor);
		DiceParser.exprList_return expressions = parser.exprList();
		Object obj = expressions.getTree();

		if(obj instanceof DiceTree)
		{
			tree = (DiceTree)obj;
		}
		else if(obj instanceof CommonErrorNode)
		{
			CommonErrorNode error = (CommonErrorNode)obj;

			throw(error.trappedException);
		}

		return tree;
	}

	public static int eval(String function) 
	{
		try 
		{
			EvalContext context = new EvalContext(null,"local");
			return eval(function,context);
		}
		catch (RecognitionException e) 
		{
			e.printStackTrace();
		} 
		catch (InvalidReferenceException e) 
		{
			e.printStackTrace();
		}
		return -1;
	}

	public static int eval(String function, EvalContext context) throws RecognitionException, InvalidReferenceException
	{
		if(!function.endsWith(";"))
		{
			function += ";";
		}

		DiceTree tree = getDiceTree(context,function);

		return tree.eval(false);
	}

	public static String parse(EvalContext context, String function)
	{
		String retval = function+" = ";
		try 
		{
			if(!function.endsWith(";"))
			{
				function += ";";
			}

			function = replaceReferences(context,function);

			DiceTree tree = getDiceTree(context,function);

			if(tree == null)
			{
				return "Your request, Roll("+function+"), seems to have some problem being evaluated.  If this seems in error, please report to dicey@area42games.com";			
			}
			
			if(tree.isNil())
			{
				int position = 0;
				for(Object t : tree.getChildren())
				{
					if(t instanceof DiceTree)
					{
						DiceTree dt = (DiceTree)t;
						int result = dt.eval(false);
						retval += dt.explain();
						retval += " = " + result + "\n";
						context.setLocal("$"+position,""+result);
					}
					position++;
				}
			}
			else
			{
				int result = tree.eval(false);
				retval += tree.explain();
				retval += " = " + result + "\n";
			}

		} 
		catch (RecognitionException e) 
		{
			return "Your request, Roll("+function+"), seems to have some problem being evaluated.  If this seems in error, please report to dicey@area42games.com";			
		} 
		catch (InvalidReferenceException e) 
		{
			return "Your request, Roll("+function+"), included a reference to a variable ("+ e.getVariable() +") that was not defined.  If this seems in error, please report to dicey@area42games.com";			
		} 
		return retval;
	}

	public static Pattern rollP = Pattern.compile("Roll\\s*\\((.*)\\)",Pattern.CASE_INSENSITIVE);
	public static Pattern privP = Pattern.compile("Priv\\s*\\((.*)\\)",Pattern.CASE_INSENSITIVE);
	public static Pattern refP = Pattern.compile("Ref\\s*\\((.*)\\)",Pattern.CASE_INSENSITIVE);
	public static Pattern setP = Pattern.compile("Set\\s*\\((.*=.*)\\)",Pattern.CASE_INSENSITIVE);
	public static Pattern helpP = Pattern.compile("(Help\\s*\\(\\))",Pattern.CASE_INSENSITIVE);
	public static Pattern donateP = Pattern.compile("(Donate\\s*\\(\\))",Pattern.CASE_INSENSITIVE);
	public static Pattern feedbackP = Pattern.compile("(Feedback\\s*\\(\\))",Pattern.CASE_INSENSITIVE);

	public static List<String> findStatements(Pattern p, String input)
	{
		List<String> retval = new ArrayList<String>();

		int start = 0;
		int count = 0;
		boolean ready = false;

		for(int i = 0; i < input.length(); i++)
		{
			if(input.charAt(i) == '(')
			{
				ready = true;
				count++;
			}
			else if(input.charAt(i) == ')')
			{
				count--;
				if(count == 0 && ready)
				{
					ready = false;
					String temp = input.substring(start,i+1);
					Matcher m = p.matcher(temp);
					if(m.find())
					{
						retval.add(m.group(1));
					}
					start = i;
				}
			}
		}

		return retval;
	}

	private static Pattern referencePattern = Pattern.compile("\\$\\!?[a-zA-Z][a-zA-Z0-9]*");

	private static String replaceReferences(EvalContext context, String input) throws InvalidReferenceException
	{
		int replaceLimit = 100;
		Matcher m = referencePattern.matcher(input);
		while(m.find() && replaceLimit > 0)
		{
			replaceLimit--;
			String ref = m.group();
			String value = context.get(ref);
			if(value != null)
			{
				input = input.replace(ref,value);
				m.reset(input);
			}
		}

		return input;
	}


	public static void main(String args[]) throws RecognitionException, InvalidReferenceException
	{
		EvalContext context = new EvalContext(null,"local");
		System.out.println(parse(context,"1d20"));

		context.set("$d8", "d10{1:-1,2..5:0,6..10:1} rollagain 8..10");
		context.set("$!mine", "d6");
		context.set("$mine", "d6+10");

		context.set("$G","3d6 -");
		context.set("$!ST","11");
		context.set("$!DX","14");
		context.set("$!IQ","13");
		context.set("$!HT","12");
		context.set("$!acting","$G $!IQ+0");

		System.out.println(parse(context,"$!acting"));

		System.out.println(parse(context,""));

		System.out.println(parse(context,"2 $mine; 2 $!mine"));
		System.out.println(parse(context,"$ST"));

		System.out.println(parse(context,"4 $d8; 1d10; $0+$1"));

		System.out.println(parse(context,"-2;3-1;1+1;2d6;3*5;6/2;"));
		System.out.println(parse(context,"6@4d6 high 3"));
		System.out.println(parse(context,"6@4d6-1"));
		System.out.println(parse(context,"6<=10"));
		System.out.println(parse(context,"1d10{1:-1,2..5:0,6..10:1}"));
		System.out.println(parse(context,"1d10{-1,0,0,0,0,1,1,1,1,1}"));
		System.out.println(parse(context,"1d10>=6?1:0"));
		System.out.println(parse(context,"2*(1+3d6)"));
		System.out.println(parse(context,"10@4d6 high 3 reroll 1"));
		System.out.println(parse(context,"4d10 rollagain 8..10 {1:-1,2..5:0,6..10:1}"));
		System.out.println(parse(context,"5d10 {1:-1,2..5:0,6..10:1}"));
	}
}
